/*
 * Copyright 2008-2023 dexian.vip. All rights reserved.
 * Support: http://www.dexian.vip
 * License: http://www.dexian.vip/license
 */

package vip.dexian.code.utils;

import vip.dexian.common.utils.SpringUtils;
import freemarker.core.Environment;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.BeansWrapperBuilder;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.DeepUnwrap;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;


/**
 * FreeMarker工具类
 *
 * @author 挺好的 2023年05月30日 15:53
 */
public final class FreeMarkerUtils {

    /**
     * 类型转换工具。除了内置的一些类型转换，还可以自定义类型转换。
     */
    private static final ConvertUtilsBean CONVERT_UTILS = BeanConvertUtils.getConvertUtils();

    /**
     * Freemarker 默认配置
     */
    private static final Configuration DEFAULT_CONFIGURATION = new Configuration(Configuration.VERSION_2_3_32);

    /**
     * 对象包装器。选择最佳匹配对象的模板模型包装对象。如
     * 1. null -> null Model，
     * 2. Number -> NumberModel
     * 3. Date -> DateModel
     * 4. String -> StringModel
     * <p>
     * 如果对象是TemplateModel则返回TemplateModel
     * 如果没有匹配到，则返回StringModel
     */
    private static final BeansWrapper DEFAULT_BEANS_WRAPPER = new BeansWrapperBuilder(
            Configuration.VERSION_2_3_32).build();


    private FreeMarkerUtils () {
    }

    /**
     * 处理模板，该方法适用于纯静态文件生成（不包含需要替换的属性）
     *
     * @param template
     *         模板内容
     *
     * @return 处理后的内容
     *
     * @throws java.io.IOException
     * @throws freemarker.template.TemplateException
     */
    public static String process (String template) throws IOException, TemplateException {
        return process(template, null);
    }

    /**
     * 处理模板
     *
     * @param template
     *         模板内容
     * @param model
     *         模型，通常用于存放需要替换的内容。 如：${createDate}
     *
     * @return 处理 后的结果
     *
     * @throws java.io.IOException
     * @throws freemarker.template.TemplateException
     */
    public static String process (String template, Map <String, ?> model) throws IOException, TemplateException {
        Configuration configuration = null;
        ApplicationContext applicationContext = SpringUtils.getApplicationContext();

        if (applicationContext != null) {
            FreeMarkerConfigurer freeMarkerConfigurer = SpringUtils.getBean("freeMarkerConfigurer",
                    FreeMarkerConfigurer.class
            );

            configuration = freeMarkerConfigurer.getConfiguration();
        }

        return process(template, model, configuration);
    }

    /**
     * 处理模板
     *
     * @param template
     *         模板内容
     * @param model
     *         模型，通常用于存放需要替换的内容。 如：${createDate}
     * @param configuration
     *         配置
     *
     * @return 处理 后的结果
     *
     * @throws java.io.IOException
     * @throws freemarker.template.TemplateException
     */
    public static String process (String template, Map <String, ?> model, Configuration configuration)
            throws IOException, TemplateException {
        if (template == null) {
            return null;
        }
        StringWriter out = new StringWriter();
        new Template("template", new StringReader(template),
                configuration != null ? configuration : DEFAULT_CONFIGURATION
        ).process(model, out);
        return out.toString();
    }

    /**
     * 获取指定类型的参数
     *
     * @param name
     *         参数名称
     * @param type
     *         参数类型
     * @param params
     *         参数{@link freemarker.template.TemplateModel}
     * @param <T>
     *
     * @return 如果值为null, 返回null。否则返回指定类型的值。{@link org.apache.commons.beanutils.ConvertUtilsBean#convert(Object, Class)}
     *
     * @throws freemarker.template.TemplateModelException
     *         {@link freemarker.template.utility.DeepUnwrap#unwrap(freemarker.template.TemplateModel)} }
     * @throws IllegalArgumentException
     *         name || type || params 为空
     */
    public static <T> T getParameter (String name, Class <T> type, Map <String, TemplateModel> params)
            throws TemplateModelException {
        Assert.hasText(name, "参数名称不能为空");
        Assert.notNull(type, "参数类型不能为空");
        Assert.notNull(params, "参数不能为空");

        TemplateModel templateModel = params.get(name);
        if (templateModel != null) {
            Object value = DeepUnwrap.unwrap(templateModel);
            if (value != null) {
                return (T) CONVERT_UTILS.convert(value, type);
            }
        }
        return null;
    }

    /**
     * 获取参数
     *
     * @param index
     *         参数索引。从0开始
     * @param type
     *         参数类型
     * @param arguments
     *         参数列表
     * @param <T>
     *         指定类型
     *
     * @return 如果 index < 0 || index > arguments.size || 无法获取到相应的参数，返回null。 否则返回对应的值。{@link org.apache.commons.beanutils.ConvertUtilsBean#convert(Object, Class)}
     *
     * @throws freemarker.template.TemplateModelException
     */
    public static <T> T getArgument (int index, Class <T> type, List <?> arguments) throws TemplateModelException {
        Assert.notNull(type, "类型不能为空");
        Assert.notNull(arguments, "参数列表不能为空");

        if (index >= 0 && index < arguments.size()) {
            Object argument = arguments.get(index);
            Object value;
            if (argument != null) {
                if (argument instanceof TemplateModel templateModel) {
                    value = DeepUnwrap.unwrap(templateModel);
                } else {
                    value = argument;
                }
                if (value != null) {
                    return (T) CONVERT_UTILS.convert(value, type);
                }
            }
        }
        return null;
    }

    /**
     * 获取变量
     *
     * @param name
     *         变量名称
     * @param env
     *         表示模板处理期间运行时环境的对象
     *
     * @return 代表 FreeMarker 模板语言 (FTL) 数据类型的接口的通用超级接口。{@link freemarker.template.TemplateModel}
     *
     * @throws freemarker.template.TemplateModelException
     */
    public static TemplateModel getVariable (String name, Environment env) throws TemplateModelException {
        Assert.hasText(name, "变量名称不能为空");
        Assert.notNull(env, "运行时环境不能为空");

        return env.getVariable(name);
    }

    /**
     * 设置变量
     *
     * @param name
     *         变量名称
     * @param value
     *         变量值
     * @param env
     *         表示模板处理期间运行时环境的对象
     *
     * @throws freemarker.template.TemplateException
     */
    public static void setVariable (String name, Object value, Environment env) throws TemplateException {
        Assert.hasText(name, "变量名称不能为空");
        Assert.notNull(env, "运行时环境不能为空");

        if (value instanceof TemplateModel) {
            env.setVariable(name, (TemplateModel) value);
        } else {
            env.setVariable(name, DEFAULT_BEANS_WRAPPER.wrap(value));
        }
    }

    /**
     * 设置变量
     *
     * @param variables
     *         变量内容。 以Map形式存储。{@link java.util.Map}
     * @param env
     *         表示模板处理期间运行时环境的对象
     *
     * @throws freemarker.template.TemplateException
     */
    public static void setVariables (Map <String, Object> variables, Environment env) throws TemplateException {
        Assert.notNull(variables, "变量内容不能为空");
        Assert.notNull(env, "运行时环境不能为空");

        for (Map.Entry <String, Object> entry : variables.entrySet()) {
            String name = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof TemplateModel) {
                env.setVariable(name, (TemplateModel) value);
            } else {
                env.setVariable(name, DEFAULT_BEANS_WRAPPER.wrap(value));
            }
        }
    }

}
